package com.eight.cloud.payment.controller;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.eight.cloud.client.model.dto.RefundDto;
import com.eight.cloud.client.model.vo.NoticePayInfo;
import com.eight.cloud.client.model.vo.NoticeRefundInfo;
import com.eight.cloud.core.global.model.BaseUser;
import com.eight.cloud.openfeign.admin.api.AppClient;
import com.eight.cloud.openfeign.message.api.MessageClient;
import com.eight.cloud.openfeign.message.model.SocketMessage;
import com.eight.cloud.redisson.utils.RedisUtils;
import com.eight.cloud.springmvc.utils.thread.IncreasingIntervalScheduler;
import com.eight.cloud.core.global.result.Result;
import com.eight.cloud.core.utils.format.ObjectFormat;
import com.eight.cloud.payment.config.WechatServiceProviderConfig;
import com.eight.cloud.payment.entity.SubPayOrderEntity;
import com.eight.cloud.payment.entity.SubRefundOrderEntity;
import com.eight.cloud.payment.facade.OrderFacade;
import com.eight.cloud.payment.model.vo.NoticeVo;
import com.eight.cloud.payment.service.ISubPayOrderService;
import com.eight.cloud.payment.service.ISubRefundOrderService;
import com.squareup.okhttp.*;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.AmountReq;
import com.wechat.pay.java.service.refund.model.CreateRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.stream.Collectors;

import static com.eight.cloud.core.global.constant.NumConstant.TEN;
import static com.eight.cloud.core.global.constant.NumConstant.THREE;
import static com.eight.cloud.core.global.constant.StrConstant.TEXT_PLAIN;
import static com.eight.cloud.payment.common.constant.CacheConstant.PAY_NOTIFY_URL_CACHE_KEY;
import static com.eight.cloud.payment.common.constant.CacheConstant.generateKey;
import static com.eight.cloud.payment.core.wechat.channel.WxPay.PAY_REFUND_NOTIFY_URL;

/**
 * 回调接口
 *
 * @ClassName: PaymentBusinessController
 * @Author: TXC
 * @Date: 2024-10-22 14:18
 **/
@Slf4j
@RestController
@RequestMapping("/notice")
public class NotificationController {
    @Resource
    private RSAAutoCertificateConfig rsaAutoCertificateConfig;
    @Resource
    private MessageClient messageClient;
    @Resource
    private RedisUtils redisUtils;
    @Resource
    private ISubPayOrderService subPayOrderService;
    @Resource
    private ISubRefundOrderService subRefundOrderService;
    @Resource
    private OrderFacade orderFacade;
    @Resource
    private AppClient appClient;
    private final OkHttpClient httpClient = new OkHttpClient();

    /**
     * 支付通知回调函数
     *
     * @param request 请求体
     */
    @PostMapping("/payment/wechat/notify_url")
    public NoticeVo paymentNotifyUrl(HttpServletRequest request) throws IOException {
        log.info("支付回调通知");
        RequestParam requestParam = getTransaction(request);
        // 初始化 NotificationParser
        NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig);
        try {
            NoticePayInfo transaction = parser.parse(requestParam, NoticePayInfo.class);
            if (ObjectUtil.isNotNull(transaction)) {
                String notifyUrl = getClientNotifyUrl(transaction.getSubMchid(), transaction.getOutTradeNo());
                SubPayOrderEntity paySubOrder = null;
                try {
                    paySubOrder = subPayOrderService.getByUuid(transaction.getOutTradeNo());
                    transaction.setOutTradeNo(paySubOrder.getSubOrderId());
                } catch (Exception e) {
                    log.error("付款订单更新失败, {}, {}", e.getMessage(), transaction);
                }
                log.info("支付回调通知内容 {}", transaction);
                if (ObjectUtil.isNotNull(notifyUrl)) {
                    if (!requestNotifyUrl(notifyUrl, ObjectFormat.objectToStr(transaction))) {
                        new IncreasingIntervalScheduler.Builder().setMaxExecutions(THREE)
                                .setInitialInterval(TEN).setIncreasingInterval(TEN)
                                .setCallSupplier(() -> requestNotifyUrl(notifyUrl,
                                        ObjectFormat.objectToStr(transaction))).build().start();
                    }
                } else {
                    assert paySubOrder != null;
                    sendMessage(transaction, paySubOrder.getAppid(), "/payment");
                }
                if (NoticePayInfo.TradeStateEnum.SUCCESS.equals(transaction.getTradeState())) {
                    if (ObjectUtil.isNotNull(paySubOrder)) {
                        orderFacade.doSuccessPay(paySubOrder.getUuid(), transaction.getTransactionId());
                    }
                }
            }
        } catch (Exception e) {
            log.error("sign verification failed", e);
            throw new RuntimeException(e);
        }
        // 处理成功，返回 200 OK 状态码
        return NoticeVo.ok();
    }

    /**
     * 退款通知回调函数
     *
     * @param request 请求体
     */
    @PostMapping("/payment/wechat/refund/notify_url")
    public NoticeVo refundNotifyUrl(HttpServletRequest request) throws IOException {
        log.info("退款回调通知");
        RequestParam requestParam = getTransaction(request);
        // 初始化 NotificationParser
        NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig);
        try {
            NoticeRefundInfo transaction = parser.parse(requestParam, NoticeRefundInfo.class);
            if (ObjectUtil.isNotNull(transaction)) {
                SubRefundOrderEntity paySubOrderVo = null;
                try {
                    paySubOrderVo = subRefundOrderService.getByUuid(transaction.getOutRefundNo());
                    transaction.setOutTradeNo(paySubOrderVo.getSubOutTradeNo());
                    transaction.setOutRefundNo(paySubOrderVo.getSubRefundNo());
                } catch (Exception e) {
                    log.error("退款订单更新失败, {}, {}", e.getMessage(), transaction);
                }
                log.info("退款回调通知内容 {}", transaction);
                String notifyUrl = getClientNotifyUrl(transaction.getSubMchid(), transaction.getOutTradeNo());
                assert paySubOrderVo != null;
                if (ObjectUtil.isNotNull(paySubOrderVo)) {
                    if (SubRefundOrderEntity.RefundSourceFormEnum.APP.getValue().equals(paySubOrderVo.getSourceFrom())) {
                        if (StrUtil.isNotEmpty(notifyUrl)) {
                            if (!requestNotifyUrl(notifyUrl, ObjectFormat.objectToStr(transaction))) {
                                new IncreasingIntervalScheduler.Builder().setMaxExecutions(THREE)
                                        .setInitialInterval(TEN).setIncreasingInterval(TEN)
                                        .setCallSupplier(() -> requestNotifyUrl(notifyUrl,
                                                ObjectFormat.objectToStr(transaction))).build().start();
                            }
                        } else {
                            sendMessage(transaction, paySubOrderVo.getAppid(), "/refund");
                        }
                    }
                    if ("SUCCESS".equals(transaction.getRefundStatus())) {
                        orderFacade.doSuccessRefund(paySubOrderVo.getUuid(), transaction.getTransactionId());
                    }
                }
            }
        } catch (Exception e) {
            log.error("sign verification failed", e);
            throw new RuntimeException(e);
        }
        // 处理成功，返回 200 OK 状态码
        return NoticeVo.ok();
    }


    /**
     * // 验签、解密并转换成 Transaction
     *
     * @param request 请求体
     * @return 转换后的 Transaction 对象
     */
    private RequestParam getTransaction(HttpServletRequest request) throws IOException {
        String requestBody = request.getReader().lines().collect(Collectors.joining());
        return new RequestParam.Builder()
                .serialNumber(request.getHeader("Wechatpay-Serial"))
                .nonce(request.getHeader("Wechatpay-Nonce"))
                .signature(request.getHeader("Wechatpay-Signature"))
                .timestamp(request.getHeader("Wechatpay-Timestamp"))
                .signType(request.getHeader("Wechatpay-Signature-Type"))
                .body(requestBody)
                .build();
    }

    private void sendMessage(Object message, String userId, String destination) {
        SocketMessage<String> socketMessage = new SocketMessage<>();
        socketMessage.setPayload(ObjectFormat.objectToStr(message));
        BaseUser baseUser = appClient.queryAppByUuid(userId);
        socketMessage.setTo(baseUser);
        socketMessage.setDestination(destination);
        socketMessage.setContentType(TEXT_PLAIN);
        try {
            messageClient.sendWebSocketMessage(socketMessage);
            log.info("发送消息成功, {}", socketMessage);
        } catch (Exception e) {
            log.error("发送消息失败, {}, {}", e.getMessage(), socketMessage);
        }

    }

    private String getClientNotifyUrl(String subMerchantId, String outTradeNo) {
        String notifyUrl = (String) redisUtils.get(generateKey(PAY_NOTIFY_URL_CACHE_KEY, subMerchantId, outTradeNo));
        redisUtils.delete(generateKey(PAY_NOTIFY_URL_CACHE_KEY, subMerchantId, outTradeNo));
        return notifyUrl;
    }

    /**
     * 通过http发送请求
     *
     * @param notifyUrl 请求地址
     * @param body      请求体
     * @return 是否成功
     */
    private boolean requestNotifyUrl(String notifyUrl, String body) {
        try {
            Request request = new Request.Builder()
                    .url(notifyUrl)
                    .post(RequestBody.create(MediaType.parse("application/json"), body))
                    .addHeader("Content-Type", "application/json")
                    .build();
            Response response = httpClient.newCall(request).execute();
            if (response.code() == 200) {
                String result = httpClient.newCall(request).execute().body().string();
                NoticeVo noticeVo = ObjectFormat.strToObject(result, NoticeVo.class);
                return ObjectUtil.isNotNull(noticeVo) && "200".equals(noticeVo.getCode());
            }
            return false;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Resource
    private WechatServiceProviderConfig wechatServiceProviderConfig;

    @GetMapping("testRefund")
    public Result<?> testRefund(String subOrderDto, String total){
        RefundDto refund = new RefundDto();
        refund.setRefund(total);
        refund.setTotal(total);
        refund.setOutRefundNo(subOrderDto);
        refund.setSubMerchantId("1695564748");
        refund.setOutTradeNo(subOrderDto);
        return Result.ok(refundTest(refund));
    }
    public String refundTest(RefundDto refund) {
        RefundService service = new RefundService.Builder().config(rsaAutoCertificateConfig).build();
        CreateRequest request = new CreateRequest();
        request.setOutRefundNo(refund.getOutRefundNo());
        request.setOutTradeNo(refund.getOutTradeNo());
        AmountReq amountReq = new AmountReq();
        // 设置退款金额（退款金额不可大于订单金额）单位为分
        try {
            amountReq.setRefund(Long.parseUnsignedLong(refund.getRefund()));
            // 订单总金额
            amountReq.setTotal(Long.parseUnsignedLong(refund.getTotal()));
        } catch (NumberFormatException e) {
            throw new RuntimeException("金额需要是以分为单位的整数", e);
        }
        amountReq.setCurrency("CNY");
        amountReq.setFrom(null);
        request.setAmount(amountReq);
        request.setSubMchid(refund.getSubMerchantId());
        request.setNotifyUrl(wechatServiceProviderConfig.getNotifyServiceUrl() + PAY_REFUND_NOTIFY_URL);
        try {
            Refund result = service.create(request);
            result.setOutTradeNo(refund.getOutTradeNo());
            return JSONUtil.parse(result).toStringPretty();
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }


}
